import { Circle, Point, Vector } from "../../core/Layout.js"
import CanvasComponent, { CanvasComponentOptions } from "./CanvasComponent.js"
import { CircleConstraint } from "./utils/Constraint.js"
import { DragEvent } from "./utils/EventEmitter.js"

type JoystickDragEventHandler = (evt: JoystickDragEvent) => void

export enum JoystickMode {
  vertical,
  horizontal,
  all
}

export type JoystickOptions = {
  mode?: JoystickMode
  radius?: number
  onDrag?: JoystickDragEventHandler
}

export class JoystickDragEvent {
  left?: boolean
  up?: boolean
  down?: boolean
  right?: boolean
  uv: Vector
  mode: JoystickMode

  constructor(mode: JoystickMode, uv: Vector) {
    this.uv = uv
    this.mode = mode
    if([JoystickMode.vertical, JoystickMode.all].includes(mode)) {
      this.up = uv.dy > 0
      this.down = uv.dy < 0
    } 
    
    if([JoystickMode.horizontal, JoystickMode.all].includes(mode)) {
      this.left = uv.dx > 0
      this.right = uv.dx < 0
    }
  }
}

export type JoyStickOptions = CanvasComponentOptions & JoystickOptions

export default class JoyStick extends CanvasComponent {
  mode: JoystickMode
  btnPosition: Point
  dragging: boolean
  radius: number
  outerRadius: number
  innerRadius: number
  bottomRadius: number
  constraintRadius: number
  declare options: JoystickOptions & CanvasComponentOptions

  constructor(options: JoyStickOptions & CanvasComponentOptions) {
    super({
      ...options,
      draggable: true
    })
    this.mode = options.mode ?? JoystickMode.vertical
    this.dragging = false
    this.radius = options.radius ?? 70
    this.outerRadius = this.radius * 1.4
    this.constraintRadius = this.radius * 1.6
    this.innerRadius = this.radius * 0.46
    this.bottomRadius = this.radius * 0.39
  }

  drawArc(x: number, y: number, r: number, strokeStyle: string) {
    var context = this.context
    context.lineWidth = 1
    context.strokeStyle = strokeStyle
    context.beginPath()
    context.arc(x, y, r, 0, Math.PI * 2, false)
    context.closePath()
    context.stroke()
  }

  drawButton(x: number, y: number) {
    var context = this.context
    var innerWidth = this.bottomRadius * 2;
    var outerWidth = this.innerRadius * 2;

    context.fillStyle = this.dragging ? 'rgba(255,255,255,0.5)' : '#fff'
    context.beginPath()
    context.arc(x, y, innerWidth / 2, 0, Math.PI * 2, false)
    context.closePath()
    context.fill()

    context.strokeStyle = this.dragging ? 'rgba(255,255,255,0.5)' : '#fff'
    context.lineWidth = (this.innerRadius - this.bottomRadius) * 2 * 0.8
    context.beginPath()
    context.arc(x, y, outerWidth / 2, 0, Math.PI * 2, false)
    context.closePath()
    context.stroke()

  }

  drawArc2(x: number, y: number, startDeg: number, endDeg: number) {
    var outerWidth = this.outerRadius * 2;
    var innerWidth = this.radius * 2;
    var buttonWidth = this.bottomRadius * 2
    var lineWidth = (outerWidth - innerWidth) / 2
    var context = this.context
    var innerR = innerWidth / 2 + lineWidth / 2
    var buttonR = buttonWidth / 2
    var lineR = innerWidth / 2
    context.lineWidth = lineWidth 
    context.strokeStyle = 'rgba(255,255,255, 0.2)'
    context.beginPath()
    context.arc(x, y, innerR, startDeg, endDeg, false)
    context.stroke()

    context.lineWidth = 1

    context.beginPath()
    context.moveTo(x + Math.cos(startDeg) * buttonR, y + Math.sin(startDeg) * buttonR)
    context.lineTo(x + Math.cos(startDeg) * lineR, y + Math.sin(startDeg) * lineR)
    context.stroke()

    context.beginPath()
    context.moveTo(x + Math.cos(endDeg) * buttonR, y + Math.sin(endDeg) * buttonR)
    context.lineTo(x + Math.cos(endDeg) * lineR, y + Math.sin(endDeg) * lineR)
    context.stroke()
  }

  async draw(context: CanvasRenderingContext2D, dt: number): Promise<void> {
    var constractWidth = this.constraintRadius * 2;
    var outerWidth = this.outerRadius * 2;
    var innerWidth = this.radius * 2;
    var x = this.screenX
    var y = this.screenY
    var cx = x + constractWidth/2
    var cy = y + constractWidth/2
    

    if(context) {
      this.drawArc(cx, cy, constractWidth/2, 'rgba(255,255,255,0.15)')
      this.drawArc(cx, cy, outerWidth/2, 'rgba(255,255,255,0.5)')
      this.drawArc(cx, cy, innerWidth/2, 'rgba(255,255,255,0.3)')
    
      if(this.btnPosition) {
        var pos = this.btnPosition
        this.drawButton(pos.x, pos.y)
      }

      if([JoystickMode.vertical, JoystickMode.all].includes(this.mode)) {
        this.drawArc2(cx, cy, Math.PI * 1 / 4, Math.PI * 3 / 4)
        this.drawArc2(cx, cy, Math.PI * 5 / 4, Math.PI * 7 / 4)
      } 
      if([JoystickMode.horizontal, JoystickMode.all].includes(this.mode)) {
        this.drawArc2(cx, cy, -Math.PI * 1 / 4, Math.PI * 1 / 4)
        this.drawArc2(cx, cy, Math.PI * 3 / 4, Math.PI * 5 / 4)
      }
    }
  }

  drawText(posX: number, posY: number, text: string) {
    var context = this.context
    context.fillStyle = 'rgba(255,255,255, 1)'
    context.fillText(text, posX, posY, 2000)
    context.textBaseline = "middle"
    context.textAlign = "center"
  }

  async resize(): Promise<void> {
    super.resize()
    this.btnPosition = new Point(this.screenX + this.width / 2, this.screenY + this.height / 2)
  }

  async onDrag(dragEvt: DragEvent): Promise<void> {
    this.dragging = dragEvt.dragging
    var circleCenter = new Point(this.screenX + this.width / 2, this.screenY + this.height / 2)
    var circleConstraint = new CircleConstraint(new Circle(circleCenter.x, circleCenter.y, this.constraintRadius))
    if(dragEvt.dragging === false) {
      // 恢复原位置
      this.btnPosition = circleCenter.clone()
    } else {
      // 实时更新位置
      this.btnPosition = circleConstraint.constraint(new Point(dragEvt.touch.clientX, dragEvt.touch.clientY))
    }
    // 获取单位向量
    var uv = circleConstraint.getUv(this.btnPosition)
    if(this.options.onDrag) {
      this.options.onDrag(new JoystickDragEvent(this.mode, uv))
    }
  }
}
